from math import floor,ceil
import time
from twisted.internet import reactor

class SomCalculator(object):
    """
    An independent calculator class (because separation of application
    and protocol logic is a good thing).
    """
    triggers = ["!som", "!leets", "!elites", "!hit", "!warroom", "!margin"]
    prefix = "http://u1.swirve.com/players/honor-"
    postfix = ".htm"

    name = "calcs"
    help = ["Calculating different stuffs:",
            "%c6!som%c <som1> <som2> ...                      <-- SoM+SoM calculator"%(03,03),
            "%c6!elites%c <cb-total> <som-home> <som-away>    <-- CB+SoM calculator"%(03,03),
            "%c6!hit%c <his-def> <your-race> <your-ome>       <-- Calculates hits"%(03,03),
            "%c6!margin%c [on/off]                            <-- Switch safety margin on/off"%(03,03),
            "%c6!warroom%c <#specs> <#leets> <#horses> [#gens=4] [#solds=0] [#mercs=0]  <-- The [] are optional"%(03,03)]
    partials = []
    
    def command(self, firstword, user, channel, msg):
        parts = msg.split()
        
        if firstword == "!som":
            return self.som(parts)
        
        if firstword in ["!leets","!elites"]:
            if len(parts) != 4:
                return {'chanmsg': ["Usage: %c6!elites%c <cb-total> <som-home> <som-away>"%(03,03)]}
            try:
                total = int(parts[1])
                home_guess = int(parts[2])
                away_guess = int(parts[3])
            except:
                return {'chanmsg': ["Usage: %c6!elites%c <cb-total> <som-home> <som-away>"%(03,03)]}
            return self.leets(total, home_guess, away_guess)
        
        if firstword == "!hit":
            if len(parts) != 4:
                return {'chanmsg': ["Usage: %c6!hit%c <his-def> <your-race> <your-ome>"%(03,03)]}
            try:
                hisdef = int(parts[1].replace(',',''))
                race = parts[2]
                ome = float(parts[3].replace('%',''))
                newpartial = hitCalc(user, hisdef, race, ome/100.0)
            except:
                return {'chanmsg': ["Usage: %c6!hit%c <his-def> <your-race> <your-ome>"%(03,03)]}
            for p in self.partials:
                if p.player == user: 
                    self.partials.remove(p)
            newpartial = hitCalc(user, hisdef, race, ome/100.0)
            self.partials.append(newpartial)
            reactor.callLater(300,self.cleanPartial,newpartial)
            return {'chanmsg':["New hit calculation started. Use%c6 !warroom%c to complete."%(03,03)]}
        
        if firstword == "!warroom":
            if len(parts) < 4 or len(parts) > 7:
                return {'chanmsg':["Usage:%c6 !warroom%c <#specs> <#leets> <#horses> [#gens=4] [#solds=0] [#mercs=0]."%(03,03)]}
            try:
                args = []
                for a in parts[1:]:
                    args.append(int(a))
            except:
                return {'chanmsg':["Usage:%c6 !warroom%c <#specs> <#leets> <#horses> [#gens=4] [#solds=0] [#mercs=0]."%(03,03)]}
            hit = None
            for p in self.partials:
                if p.player == user:
                    hit = p
            if hit == None:
                return {'chanmsg':["First set the conditions using %c6!hit%c."%(03,03)]}
            hit.setArmy(*args)
            msg = hit.doCalc()
            ## self.partials.remove(hit)
            return {'chanmsg':[msg]}
        
        if firstword == "!margin":
            if len(parts) != 2: return {'chanmsg':["Usage: %c6!margin%c [on/off]."%(03,03)]}
            hit = None
            for p in self.partials:
                if p.player == user:
                    hit = p
            if hit == None:
                return {'chanmsg':["First set the conditions using %c6!hit%c."%(03,03)]}
            return {'chanmsg':[hit.setMargin(parts[1])]}
    
    def cleanPartial(self, partial):
        if partial in self.partials:
            self.partials.remove(partial)
    
        
    def leets(self, total, home_guess, away_guess):
        bottoms_1 = [ceil(100*(home_guess-0.5)/x) for x in range(60,141)]
        tops_1 = [floor(100*(home_guess+0.5)/x) for x in range(60,141)]
        home_1 = set(bottoms_1+tops_1)
        
        bottoms_2 = [total-ceil(100*(away_guess-0.5)/x) for x in range(60,141)]
        tops_2 = [total-floor(100*away_guess+0.5)/x for x in range(60,141)]
        home_2 = set(bottoms_2+tops_2)
        
        options = list(home_1.intersection(home_2))
        
        if len(options) == 0: return {'chanmsg':["No options found."]}
        if len(options) > 5: return {'chanmsg':["Too many options found, try another SoM or SoM+SoM."]}
        
        msg = ["%d option(s) found:"%len(options)]
        options.sort()
        options.reverse()
        msg = msg + map(lambda x: "Home:%c4 %d%c, Away:%c5 %d%c."%(03,x,03,03,total-x,03), options)
        return {'chanmsg':msg}


    def som(self, parts):
        nrofsoms = len(parts)-1
        estimates = []
        try:
            for i in range(nrofsoms):
                guess = parts[i+1]
                guess = guess.replace(',','')
                estimates.append(int(guess))
        except:
            return {'chanmsg': ["Usage: %c6!som%c <som1> <som2> ..."%(03,03)]}
        
        totals = []
        start = time.clock()
        # Actual algorithm
        for i in range(nrofsoms):
            bottoms = [ceil(100.0*(estimates[i]-0.5)/j) for j in range(70,131)]
            tops = [floor(100.0*(estimates[i]+0.5)/j) for j in range(70,131)]
            totals.append(set(bottoms+tops))
        
        options = totals.pop()
        for x in totals:
            options = options.intersection(x)
        # That's all..
        stop = time.clock()

        msg = []
        if len(options) == 0:
            msecs = 1000*(stop-start)
            msg.append("No valid options found. (%0.3f ms)"%msecs)
        elif len(options) > 5:
            msg.append("Too many options found, please get more info.")
        else:
            msg.append("%d option(s) found:"%len(options))
            options = list(options)
            options.sort()
            options.reverse()
            optstrings = map(lambda x:self.stringOption(x, estimates), options)
            msg = msg + optstrings
        return {'chanmsg': msg}
    
    def stringOption(self, option, estimates):
        str = "%c4 %d%c ("%(03,option,03)
        for e in estimates:
            str = str + "%d%% "%round(100.0*e/option)
        str = str.rstrip()
        str = str + ")"
        return str
        
class hitCalc(object):
    races = {'darkelf':(6,4,3),'dwarf':(5,7,3),'elf':(5,6,3),'orc':(5,8,3),'human':(5,6,3),'gnome':(5,5,3),'undead':(5,9,3)}
    
    def checkValid(self, moddef, race, ome):
        return True
    
    def __init__(self, player, moddef, race, ome):
        if race not in self.races.keys(): error("Invalid race choice.")
        if moddef < 0: error("Negative def?")
        if ome < 0: error("Negative OME?")
        self.player = player
        self.moddef = moddef
        self.race = race
        self.ome = ome
        self.margin = True
        
    def setMargin(self, arg):
        if not arg in ["on","off"]: return "Usage: %c6!margin%c [on/off]."%(03,03)
        if arg == "on": 
            self.margin = True
            return "Safety margin switched back on."
        else: 
            self.margin = False
            return "2% safety margin switched off.. Careful now! ;)"
    
    def setArmy(self, specs, elites, horses, gens=4, solds=0, mercs=0):
        if gens > 5: return False
        self.gens = gens
        self.specs = specs
        self.elites = elites
        self.horses = horses
        self.solds = solds
        self.mercs = mercs
        return True
    
    def doCalc(self):
        (sp,el,ho,so,me) = self.calc()
        if max(sp,el,ho,so,me) == 0:
            return "Max off only%c4 %d%c. You cannot break the target."%(03,self.calcMO((self.specs,self.elites,min(self.horses,self.specs+self.elites+self.solds),self.solds,self.mercs)),03)
        modded = self.calcMO((sp,el,ho,so,me))
        msg = "With %d gens, send"%(self.gens)
        if so: msg = msg + ", %d solds"%so
        if sp: msg = msg + ", %d specs"%sp
        if el: msg = msg + ", %d leets"%el
        if ho: msg = msg + ", %d horses"%ho
        if me: msg = msg + ", %d mercs"%me
        msg = msg[:17]+msg[18:]                 # remove the extra comma
        msg = msg + ". (MO: %d)"%modded  
        return msg
    
    def calcMO(self, amounts):
        (S,E,M) = self.races[self.race]
        raw = amounts[0]*S+amounts[1]*E+amounts[2]*1+amounts[3]*1+amounts[4]*M
        mod = floor(raw*(self.ome)*(0.97+0.03*self.gens))
        return mod            
        
    def calc(self):
        (S,E,M) = self.races[self.race]
        margin = 1.02
        if self.margin == False: margin = 1
        target = ceil(self.moddef*margin/(self.ome)/(0.97+0.03*self.gens))
        
        hspecs = min(self.horses, self.specs)
        hspecs_needed = ceil(target/(S+1))
        if hspecs >= hspecs_needed:
            return (hspecs_needed, 0, hspecs_needed, 0, 0)
        target = target - hspecs*(S+1)
        specs_left = self.specs-hspecs
        horses_left = self.horses-hspecs
        
        if specs_left > 0:
            specs_needed = ceil(target/S)
            if specs_left >= specs_needed:
                return (hspecs + specs_needed, 0, hspecs, 0, 0)
        target = target - specs_left*S
        
        helites = min(horses_left, self.elites)
        helites_needed = ceil(target/(E+1))
        if helites >= helites_needed:
            return (self.specs,helites_needed,helites_needed+hspecs, 0, 0)
        target = target - helites*(E+1)
        elites_left = self.elites - helites
        horses_left = horses_left - helites
        
        if elites_left > 0:
            elites_needed = ceil(target/E)
            if elites_left >= elites_needed:
                return (self.specs, helites+elites_needed, helites+hspecs, 0, 0)
        target = target - elites_left*E
        
        hsolds = min(horses_left, self.solds)
        hsolds_needed = ceil(target/(1+1))
        if hsolds >= hsolds_needed:
            return (self.specs,self.elites, helites+hspecs+hsolds_needed, hsolds_needed, 0)
        target = target - hsolds*(1+1)
        solds_left = self.solds-hsolds
        horses_left = horses_left-hsolds
        
        if solds_left > 0:
            solds_needed = ceil(target/1)
            if solds_left >= solds_needed:
                return (self.specs, self.elites, hspecs+helites+hsolds, hsolds+solds_needed,0)
        target = target-solds_left*1
        
        mercs_needed = ceil(target/M)
        if self.mercs >= mercs_needed:
            return (self.specs,self.elites, hspecs+helites+hsolds, self.solds, mercs_needed)
        else: 
            return (0,0,0,0,0)
        